/**
 * Copyright 2011 Renato de Jesus Manzoni
 * 
 * SED - Software Engineering and Dependability Research Group
 * 
 *     http://www.sed.ic.unicamp.br/
 * 
 * Distributed Systems Laboratory (LSD) in the Institute of Computing (IC) at University of Campinas (UNICAMP)
 * 
 *     http://www.lsd.ic.unicamp.br/
 * 
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package br.unicamp.ic.lsd.wscaa.config;

import java.io.File;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import br.unicamp.ic.lsd.wscaa.api.ExceptionHandler;
import br.unicamp.ic.lsd.wscaa.config.model.ConfigurationRules;
import br.unicamp.ic.lsd.wscaa.config.model.Handler;
import br.unicamp.ic.lsd.wscaa.config.model.Tree;

/**
 * @author Renato de Jesus Manzoni <rjmanzoni@gmail.com>
 * 
 */
public class ConfigurationManager {
    private final ConfigurationRules configRules;

    private ClientClassFactory clientHandlerFactory;

    private Map<String, String> hashExHand;

    private Map<String, List<String>> inOrderTree;

    private Map<String, List<String>> posOrderTree;

    public ConfigurationManager(ConfigurationRules configRules) {
	this.configRules = configRules;

	clientHandlerFactory = new ClientClassFactory(new File(configRules
		.getResolutionHandlers().getHandlersPath()));

	buildHashExceptionHandler();

	initializeInPosOrderList();
    }

    private void initializeInPosOrderList() {
	List<Tree> treeList = this.configRules.getrTree().getTrees();

	posOrderTree = new HashMap<String, List<String>>();
	inOrderTree = new HashMap<String, List<String>>();

	for (Iterator<Tree> iterator = treeList.iterator(); iterator.hasNext();) {
	    Tree tree = iterator.next();

	    List<String> posOrList = new ArrayList<String>();
	    createPosOrderList(tree, posOrList);
	    posOrderTree.put(tree.getNode(), posOrList);

	    List<String> inOrList = new ArrayList<String>();
	    createInOrderList(tree, inOrList);
	    inOrderTree.put(tree.getNode(), inOrList);
	}
    }

    /**
     * Build hash [exception(key), handler(value)]
     */
    private void buildHashExceptionHandler() {
	List<Handler> handlers = configRules.getResolutionHandlers()
		.getHandlers();

	hashExHand = new HashMap<String, String>();

	for (Iterator<Handler> iterator = handlers.iterator(); iterator
		.hasNext();) {
	    Handler handler = iterator.next();
	    List<String> exceptions = handler.getExceptions();
	    for (Iterator<String> iteratorEx = exceptions.iterator(); iteratorEx
		    .hasNext();) {
		String exception = iteratorEx.next();
		hashExHand.put(exception, handler.getHandlerClass());
	    }

	}
    }

    /**
     * @param tree
     */
    private void createPosOrderList(Tree t, List<String> inOrTree) {

	List<Tree> childs = t.getChilds();
	if (childs != null) {
	    for (Iterator<Tree> iterator = childs.iterator(); iterator
		    .hasNext();) {
		Tree tree = iterator.next();

		createPosOrderList(tree, inOrTree);
	    }
	}
	inOrTree.add(t.getNode());
    }

    private void createInOrderList(Tree t, List<String> posOrTree) {

	List<Tree> childs = t.getChilds();
	if (childs != null) {

	    for (Iterator<Tree> iterator = childs.iterator(); iterator
		    .hasNext();) {
		Tree tree = iterator.next();
		createInOrderList(tree, posOrTree);
		// System.out.print(i+" "+t.getRoot()+", ");
		posOrTree.add(t.getNode());

	    }
	}
	if (!posOrTree.contains(t.getNode())) {
	    // System.out.print("->"+i+" "+t.getRoot()+", ");
	    posOrTree.add(t.getNode());
	}
    }

    /**
     * @see http
     *      ://stackoverflow.com/questions/1484473/how-can-i-find-the-common-
     *      ancestor-of-two-nodes-in-a-binary-tree
     * @param exceptions
     * @return the root exception
     */
    public String findCommonLowestAncestorException(String exceptions[]) {

	String key = findTreeExceptionKey(exceptions);

	if (key != null) {
	    int minMax[] = findMinMaxIndex(exceptions, inOrderTree.get(key));

	    List<String> possiblesRoot = findPossiblesRoot(minMax[0],
		    minMax[1], inOrderTree.get(key));

	    if (possiblesRoot.size() == 1)
		return possiblesRoot.get(0);

	    List<String> porOrderlist = posOrderTree.get(key);

	    for (Iterator<String> iterator = porOrderlist.iterator(); iterator
		    .hasNext();) {
		String ex = iterator.next();
		possiblesRoot.remove(ex);
		if (possiblesRoot.size() == 1)
		    return possiblesRoot.get(0);
	    }
	}
	return null;
    }

    private String findTreeExceptionKey(String exceptions[]) {
	List<String> exceptionList = Arrays.asList(exceptions);

	for (Iterator<String> iterator = posOrderTree.keySet().iterator(); iterator
		.hasNext();) {
	    String key = iterator.next();
	    if (posOrderTree.get(key).containsAll(exceptionList))
		return key;
	}
	return null;

    }

    private List<String> findPossiblesRoot(int min, int max,
	    List<String> inOrList) {

	List<String> subInOrList = inOrList.subList(min, max);
	List<String> subListNonRepetition = new ArrayList<String>();

	for (String string : subInOrList) {
	    if (!subListNonRepetition.contains(string))
		subListNonRepetition.add(string);
	}

	return subListNonRepetition;
    }

    private int[] findMinMaxIndex(String[] exceptions, List<String> inOrList) {
	int min = 100000;
	int max = -1;

	for (int i = 0; i < exceptions.length; i++) {
	    int index = inOrList.indexOf(exceptions[i]);

	    if (index < min)
		min = index;
	    if (index > max)
		max = index;
	}

	return new int[] { min + 1, max };
    }

    public ExceptionHandler getHandlerInstance(String exception) {
	return clientHandlerFactory
		.createHandlerInstance(getHandler(exception));
    }

    public String getHandler(String exception) {
	return hashExHand.get(exception);
    }

    public List<String> getInOrderList(String tree) {
	return inOrderTree.get(tree);
    }

    public List<String> getPosOrderList(String tree) {
	return posOrderTree.get(tree);
    }
}
